package com.daxue.common.http.core;

import com.daxue.common.http.exception.RestConnectException;
import com.daxue.common.http.exception.RestContentException;
import com.daxue.common.http.utils.CollectionsUtils;
import org.apache.http.HttpHost;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.boot.web.client.RestTemplateCustomizer;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.BufferingClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;

import java.net.*;
import java.util.*;

/**
 * @author daxue0929
 * @date 2022/4/7
 */

public abstract class RestClientImpl implements RestClient {

    private static final String DEFAULT_CLIENT_NAME = "anonymous client";
    private String clientName;

    public String getClientName() {
        if (Objects.nonNull(this.clientName)) {
            return this.clientName;
        } else {
            for (Class inter : this.getClass().getInterfaces()) {
                if (ClassUtils.isAssignable(RestClient.class, inter)) {
                    this.clientName = inter.getSimpleName();
                    return this.clientName;
                }
            }
            return DEFAULT_CLIENT_NAME;
        }
    }

    private static final Logger log = LoggerFactory.getLogger(RestClientImpl.class);
    //private RateLimiterBuilder rateLimiterBuilder;
    private RestClientResourceConfig clientConfig;
    private RestTemplate restTemplate;
    private RestClient proxy;

    public RestClientImpl() {
    }

    //@Autowired
    //public void setRateLimiterBuilder(RateLimiterBuilder rateLimiterBuilder) {
    //    this.rateLimiterBuilder = rateLimiterBuilder;
    //}

    public void reConfig(RestClientResourceConfig clientConfig) {
        this.clientConfig = (RestClientResourceConfig) Optional.ofNullable(clientConfig).orElseGet(RestClientResourceConfig::new);
        this.reInitiate();
    }

    public void reInitiate() {

        HttpClientBuilder clientBuilder = HttpClientBuilder.create().useSystemProperties();
        if (StringUtils.hasText(this.clientConfig.getProxy())) {
            clientBuilder.setProxy(HttpHost.create(this.clientConfig.getProxy()));
        }

        this.restTemplate = new RestTemplateBuilder()
            .requestFactory(() -> new BufferingClientHttpRequestFactory(customClientHttpRequestFactory()))
            //.additionalInterceptors(
            //    new ClientHttpRequestInterceptor[]{
            //        new RequestRateLimiterInterceptor(
            //            this.rateLimiterBuilder,
            //            this.getClientName(),
            //            (List) Optional.ofNullable(this.clientConfig.getRateLimits()).orElseGet(CollectionsUtils::listOf)
            //        )
            //    }
            //)
            .additionalInterceptors(
                new ClientHttpRequestInterceptor[] {
                    new RequestLoggingInterceptor(
                        this.getClientName(),
                        (Set) Optional.ofNullable(this.clientConfig.getLogging()).orElseGet(CollectionsUtils::setOf)
                    )
                }
            )
            .setConnectTimeout(this.clientConfig.connectDuration())
            .setReadTimeout(this.clientConfig.readDuration())
            .build();




    }

    public ClientHttpRequestFactory customClientHttpRequestFactory() {

//        SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = new SimpleClientHttpRequestFactory();
//        if (StringUtils.hasText(this.clientConfig.getProxy())) {
//            HttpHost host = HttpHost.create(this.clientConfig.getProxy());
//            try {
//                InetAddress inetAddress = InetAddress.getByName(host.getHostName());
//                simpleClientHttpRequestFactory.setProxy(new Proxy(
//                    Proxy.Type.HTTP,
//                    new InetSocketAddress(inetAddress, host.getPort())
//                ));
//            } catch (UnknownHostException var4) {
//                log.error("The restTemplate proxy has unknown host", var4);
//            }
//        }
//        return simpleClientHttpRequestFactory;

        /**
         * 解决默认restTemplate不支持patch方法问题。
         */
        PoolingHttpClientConnectionManager poolingConnectionManager = new PoolingHttpClientConnectionManager();
        poolingConnectionManager.setMaxTotal(3000); // 最大连接数
        poolingConnectionManager.setDefaultMaxPerRoute(500); // 单路由的并发数 500
        HttpClientBuilder httpClientBuilder = HttpClients.custom();
        if (StringUtils.hasText(clientConfig.getProxy())) {
            httpClientBuilder.setProxy(HttpHost.create(clientConfig.getProxy())); // 设置代理
        }
        httpClientBuilder.setConnectionManager(poolingConnectionManager);
        HttpClient httpClient = httpClientBuilder.build();
        return new HttpComponentsClientHttpRequestFactory(httpClient);
    }

    public void aware(final RestClient proxy) {
        this.proxy = proxy;
    }

    public RestClient selfProxy() {
        return this.proxy;
    }

    public final <T, R> ResponseEntity<R> exchange(
        URI uri,
        HttpMethod method,
        HttpHeaders headers,
        T body,
        ParameterizedTypeReference<R> typeReference
    ) throws RestConnectException, RestContentException {
        return ((RestClient) Optional.ofNullable(this.selfProxy()).orElse(this)).doExchange(uri, method, headers, body, typeReference);
    }

    public final <T> ResponseEntity<T> get(URI uri, HttpHeaders headers, ParameterizedTypeReference<T> typeReference) throws
        RestConnectException,
        RestContentException {
        return this.exchange(uri, HttpMethod.GET, headers, (Object) null, typeReference);
    }

    public final <T> ResponseEntity<T> get(URI uri, ParameterizedTypeReference<T> typeReference) throws
        RestConnectException,
        RestContentException {
        return this.exchange(uri, HttpMethod.GET, RestClient.jsonHeader(), (Object) null, typeReference);
    }

    public final ResponseEntity<Map<String, Object>> get(URI uri, HttpHeaders headers) throws
        RestConnectException,
        RestContentException {
        return this.exchange(uri, HttpMethod.GET, headers, (Object) null, MAP_RESPONSE_TYPE);
    }

    public final ResponseEntity<Map<String, Object>> get(URI uri) throws RestConnectException, RestContentException {
        return this.exchange(uri, HttpMethod.GET, RestClient.jsonHeader(), (Object) null, MAP_RESPONSE_TYPE);
    }

    public final <T, R> ResponseEntity<R> post(
        URI uri,
        HttpHeaders headers,
        T body,
        ParameterizedTypeReference<R> typeReference
    ) throws RestConnectException, RestContentException {
        return this.exchange(uri, HttpMethod.POST, headers, body, typeReference);
    }

    public final <T, R> ResponseEntity<R> post(URI uri, T body, ParameterizedTypeReference<R> typeReference) throws
        RestConnectException,
        RestContentException {
        return this.exchange(uri, HttpMethod.POST, RestClient.jsonHeader(), body, typeReference);
    }

    public final <T> ResponseEntity<Map<String, Object>> post(URI uri, HttpHeaders headers, T jsonBody) throws
        RestConnectException,
        RestContentException {
        return this.exchange(uri, HttpMethod.POST, headers, jsonBody, MAP_RESPONSE_TYPE);
    }

    public final ResponseEntity<Map<String, Object>> post(URI uri, HttpHeaders headers) throws
        RestConnectException,
        RestContentException {
        return this.exchange(uri, HttpMethod.POST, headers, (Object) null, MAP_RESPONSE_TYPE);
    }

    public final <T> ResponseEntity<Map<String, Object>> post(URI uri, T jsonBody) throws RestConnectException, RestContentException {
        return this.exchange(uri, HttpMethod.POST, RestClient.jsonHeader(), jsonBody, MAP_RESPONSE_TYPE);
    }

    public final <T, R> ResponseEntity<R> put(
        URI uri,
        HttpHeaders headers,
        T jsonBody,
        ParameterizedTypeReference<R> typeReference
    ) throws RestConnectException, RestContentException {
        return this.exchange(uri, HttpMethod.PUT, headers, jsonBody, typeReference);
    }

    public final <T, R> ResponseEntity<R> put(URI uri, T jsonBody, ParameterizedTypeReference<R> typeReference) throws
        RestConnectException,
        RestContentException {
        return this.exchange(uri, HttpMethod.PUT, RestClient.jsonHeader(), jsonBody, typeReference);
    }

    public final <T> ResponseEntity<Map<String, Object>> put(URI uri, HttpHeaders headers, T jsonBody) throws
        RestConnectException,
        RestContentException {
        return this.exchange(uri, HttpMethod.PUT, headers, jsonBody, MAP_RESPONSE_TYPE);
    }

    public final ResponseEntity<Map<String, Object>> put(URI uri, HttpHeaders headers) throws
        RestConnectException,
        RestContentException {
        return this.exchange(uri, HttpMethod.PUT, headers, (Object) null, MAP_RESPONSE_TYPE);
    }

    public final <T> ResponseEntity<Map<String, Object>> put(URI uri, T jsonBody) throws RestConnectException, RestContentException {
        return this.exchange(uri, HttpMethod.PUT, RestClient.jsonHeader(), jsonBody, MAP_RESPONSE_TYPE);
    }

    public final <T> ResponseEntity<T> delete(URI uri, HttpHeaders headers, ParameterizedTypeReference<T> typeReference) throws
        RestConnectException,
        RestContentException {
        return this.exchange(uri, HttpMethod.DELETE, headers, (Object) null, typeReference);
    }

    public final <T> ResponseEntity<T> delete(URI uri, ParameterizedTypeReference<T> typeReference) throws
        RestConnectException,
        RestContentException {
        return this.exchange(uri, HttpMethod.DELETE, RestClient.jsonHeader(), (Object) null, typeReference);
    }

    public final ResponseEntity<Map<String, Object>> delete(URI uri, HttpHeaders headers) throws
        RestConnectException,
        RestContentException {
        return this.exchange(uri, HttpMethod.DELETE, headers, (Object) null, MAP_RESPONSE_TYPE);
    }

    public final ResponseEntity<Map<String, Object>> delete(final URI uri) throws RestConnectException, RestContentException {
        return this.exchange(uri, HttpMethod.DELETE, RestClient.jsonHeader(), (Object) null, MAP_RESPONSE_TYPE);
    }

    public RestClientResourceConfig getClientConfig() {
        return this.clientConfig;
    }

    public RestTemplate getRestTemplate() {
        return this.restTemplate;
    }

}
